package com.lpc.redis.shizhan;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.ZParams;

public class Chapter01 {
	
	private static final int  ONE_WEEK_SECONDS = 7* 24 *60*60;	//一周时间
	private static final int  VOTE_SCORE  = 432;	//评分
	private static final int  ARTICLES_PER_PAGE = 25;
	public static void main(String[] args) {
		System.out.println( System.currentTimeMillis() / 1000);
		new Chapter01().run();
	}
	private void run() {
		Jedis conn = new Jedis("localhost");
		conn.select(15);
		String articleId = postArticle(conn, "蜂巢", "分享从此刻开始", "http://www.google.com");
		System.out.println("我添加新的文章,id是" + articleId);
		System.out.println("它的hash存储 是");
		 Map<String,String> articleData = conn.hgetAll("article:" + articleId);
		for (Map.Entry<String,String> entry : articleData.entrySet()){
            System.out.println("  " + entry.getKey() + ": " + entry.getValue());
        }
		 articleVote(conn, "other_user", "article:" + articleId);
	        String votes = conn.hget("article:" + articleId, "votes");
	        System.out.println("我们点赞文章id,它现在的评分: " + votes);
	        assert Integer.parseInt(votes) > 1;

	        System.out.println("目前最高评分的文章是:");
	        List<Map<String,String>> articles = getArticles(conn, 1);
	        printArticles(articles);
	        assert articles.size() >= 1;

	        addGroups(conn, articleId, new String[]{"new-group"});
	        System.out.println("我们添加一篇文章到新组中，组中还有其他的文章包括:");
	        articles = getGroupArticles(conn, "new-group", 1);
	        printArticles(articles);
	        assert articles.size() >= 1;

	}
	/**
	 * 新建，存储一篇文章
	 * 1.使用hash存储文章
	 * 2.根据发布评分，发布时间排序文章的有序集合
	 * @param conn redis
	 * @param user 用户名称
	 * @param title 标题
	 * @param link 网址链接
	 * @return 字符串articleId 
	 */
	public String postArticle(Jedis conn, String user, String title, String link) {
		//conn.incr("article") 将key中储存的数字值增一。如果key不存在，以0为key的初始值，然后执行INCR操作。返回值  执行INCR命令之后key的值。
		String articleId  = String.valueOf(conn.incr("article"));//生成一个新文章ID
		//将发布文章的用户添加到文章已投票用户名单里面，然后将这个名单的攻击时间设置为一周
		String voted = "voted:" + articleId;
		conn.sadd(voted, user); //将一个或多个member元素加入到集合key当中，已经存在于集合的member元素将被忽略。
		conn.expire(voted, ONE_WEEK_SECONDS);
		//使用hash存储文章信息
		long now = System.currentTimeMillis() / 1000;
		String article = "article:" + articleId;
		HashMap<String, String> articleData = new HashMap<String,String>();
		articleData.put("title", title);
		articleData.put("link", link);
		articleData.put("user", user);
		articleData.put("now", String.valueOf(now));
		articleData.put("votes", "1");
		conn.hmset(article, articleData);
		//根据发布评分，发布时间排序文章的有序集合
		conn.zadd("score:", now + VOTE_SCORE, article);
		conn.zadd("time:", now, article);
		return articleId;
	}
	/**
	 * 投票
	 * 1.添加有序集合 score: 评分
	 * 2.添加hash表里评分
	 * @param conn Jedis
	 * @param user 用户
	 * @param article 文章id
	 */
	public void articleVote(Jedis conn, String user,String article) {
		long cutoff = ( System.currentTimeMillis() / 1000 ) - ONE_WEEK_SECONDS; //计算文章投票截止时间
		//检查是否还可以对文章进行投票（虽然使用散列也可以获取文章的发布时间，但有序集合返回的文章发布时间为浮点数，可以不进行转化直接使用）
		if (conn.zscore("time:", article) < cutoff) {
			return;
		}
		String articleId = article.substring(article.indexOf(":") + 1);//从article:id标识符里面取出文章的ID
		//如果用户是第一次为这篇文章投票，那么增加这篇文章的投票和评分
		if (conn.sadd("voted:"+ articleId, user) == 1) {
			conn.zincrby("score:", VOTE_SCORE, article); //为有序集key的成员member的score值加上增量increment。
			conn.hincrBy(article, "votes", 1l);//为哈希表key中的域field的值加上增量increment。
		}
	}
	
	/**
	 * 按评分文章排序
	 * @param conn
	 * @param page
	 * @return
	 */
	  public List<Map<String,String>> getArticles(Jedis conn, int page) {
	        return getArticles(conn, page, "score:");
	    }
	/**
	 * 获取文章信息
	 * @param conn Jedis
	 * @param page 分页
	 * @param order 
	 * @return
	 */
	public List<Map<String, String>> getArticles(Jedis conn,int page ,String order) {
		//设置获取文章的起始索引和结束索引
		int start = (page -1 ) * ARTICLES_PER_PAGE;
		int end  = start +ARTICLES_PER_PAGE -1;
		//获取多个文章id
		Set<String> ids = conn.zrevrange(order, page, end); //返回有序集key中，指定区间内的成员。
		List<Map<String, String>> articles = new ArrayList<Map<String,String>>();
		//根据文章ID获取文章详细信息
		for (String id : ids) {
			Map<String, String> articleData = conn.hgetAll(id);
			articleData.put("id", id);
			articles.add(articleData);
		}
		return articles;
	}
	
	
	/**
	 * 对文章进行分组
	 * @param conn Jedis
	 * @param articleId 文章ID
	 * @param toAdd 文章信息数组
	 */
	public  void addGroups(Jedis conn,String articleId,String[] toAdd) {
		String article = "article:" + articleId;  //构建存储文章信息的键名
		for (String group : toAdd) {    //将文章添加到它的所属群组里
			conn.sadd("group:" + group, article);
		}
	}
	/**
	 * 分组按照评分排序
	 * @param conn
	 * @param group
	 * @param page
	 * @return
	 */
	 public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page) {
	        return getGroupArticles(conn, group, page, "score:");
	    }
	
	/**
	 * 获取分组之后文章
	 * @param conn
	 * @param group
	 * @param page
	 * @param order
	 * @return
	 */
	public List<Map<String, String>> getGroupArticles(Jedis conn, String group, int page,String order) {
	String key = group + order; // 为每个群组的每种排列循序都创建一个键
	if (!conn.exists(key)) { //检查是否有已缓存的排序结果，如果没有现在开始排序
		ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX); //根据评分或者发布时间对群组文章进行排序
		conn.zinterstore(key, params, "group:" + group,order);
		conn.expire(key, 60);  //让redis60秒之后删除这个有序集合
	}
	return getArticles(conn, page, key);//进行分页并获取文章数据
}	
	/**
	 * 打印文章
	 * @param articles
	 */
	private void printArticles(List<Map<String,String>> articles){
        for (Map<String,String> article : articles){
            System.out.println("  id: " + article.get("id"));
            for (Map.Entry<String,String> entry : article.entrySet()){
                if (entry.getKey().equals("id")){
                    continue;
                }
                System.out.println("    " + entry.getKey() + ": " + entry.getValue());
            }
        }
    }
	
	
	
}
